package com.airport.schedule.genetic;

import com.airport.schedule.Problem;
import com.airport.schedule.ScheduleEvaluator;
import com.airport.schedule.ScheduleSolution;
import com.airport.schedule.model.AdHocRule;
import com.airport.schedule.model.Flight;
import com.airport.schedule.model.FlightGate;
import com.airport.schedule.util.FileUtil;
import com.airport.schedule.util.ListUtil;

import java.io.IOException;
import java.util.*;

public class ImproveGeneticScheduler {

    private Problem problem;
    private Genotype genotype;
    private ScheduleSolution solution;

    private int maxIterations;
    private int totalFlight;
    private int totalPeople;
    private int popSize;

    private Random random = new Random(System.currentTimeMillis());

    public ImproveGeneticScheduler(Problem problem, int totalFlight, int totalPeople, String file,
                                   int popSize, double muteRate, double crossRate, int maxIterations)
            throws IOException {

        this.popSize = popSize;
        this.problem = problem;
        this.maxIterations = maxIterations;
        this.totalFlight = totalFlight;
        this.totalPeople = totalPeople;

        List<String[]> records = FileUtil.readCsvFile(file);
        Map<String,String> greedyPlan = new HashMap<>();
        records.forEach(r-> greedyPlan.put(r[0], r[3]));
        List<Flight> flights = problem.getScheduleFlights();
        Set<String> flightIds = new HashSet<>(ListUtil.map(records, r->r[0]));
        List<Flight> rmFlights = new ArrayList<>();
        flights.forEach(f->{
            if(!flightIds.contains(f.getFlightId()))
                rmFlights.add(f);
        });
        flights.removeAll(rmFlights);

        int numGenes = problem.getScheduleFlights().size();
        int[][] valids = new int[numGenes][];
        for (int i = 0; i < numGenes; i++){
            Flight flight = problem.getFlightAt(i);
            List<Integer> validAlleles = new ArrayList<>();
            List<Integer> possibleAlleles = new ArrayList<>();
            for(int j = 0; j < problem.getScheduleGates().size(); j++) {
                FlightGate gate = problem.getFlightGateAt(j);
                if(gate.acceptFlight(flight)) {
                    possibleAlleles.add(j);
                    if(gate.isAdHocGate()) {
                        if(gate.getRule().getRuleType() == AdHocRule.IS_RUNWAY_GATE
                                && (flight.isStayOverNight() ||
                                flight.getDepartureTime() - flight.getArrivalTime() <= 3 * 3600 )) {
                            validAlleles.add(j);
                        }else if(gate.getRule().getRuleType() == AdHocRule.IS_COMPOSITE
                                && flight.getDepartureTime() - flight.getArrivalTime() <= 3 * 3600) {
                            validAlleles.add(j);
                        }
                    }
                    if(nearGateHeuristic(flight) && gate.isNearGate()) {
                        validAlleles.add(j);
                    }
                    if(farGateHeuristic(flight) && !gate.isNearGate()) {
                        validAlleles.add(j);
                    }
                }
            }
            validAlleles = validAlleles.isEmpty() ? possibleAlleles : validAlleles;
            int[] candidates = new int[validAlleles.size()];
            for(int k = 0; k < candidates.length; k++) {
                candidates[k] = validAlleles.get(k);
            }
            valids[i] = candidates;
        }

        List<int[]> chromosomes = initChromosomes(greedyPlan, valids);

        this.genotype = new Genotype(popSize, muteRate, crossRate, 0.98, valids,
                chromosomes, this::evaluate);
        this.genotype.setMaximizer();
    }

    private List<int[]> initChromosomes(Map<String,String> greedyPlan, int[][] validGenes) {
        List<int[]> chromosomes = new ArrayList<>();
        int[] greedyChromosome = new int[problem.getScheduleFlights().size()];
        for(int i = 0; i < greedyChromosome.length; i++) {
            String flightId = problem.getFlightAt(i).getFlightId();
            String gateId = greedyPlan.get(flightId);
            FlightGate gate = problem.getFlightGateById(gateId);
            greedyChromosome[i] = problem.indexOf(gate);
        }
        chromosomes.add(greedyChromosome);
        while (chromosomes.size() < popSize) {
            int[] chromosome = new int[greedyChromosome.length];
            System.arraycopy(greedyChromosome, 0, chromosome, 0, chromosome.length);
            for(int i = 0; i < chromosome.length; i++) {
                if(random.nextDouble() < 0.05) {
                    chromosome[i] = validGenes[i][random.nextInt(validGenes[i].length)];
                }
            }
            chromosomes.add(chromosome);
        }
        return chromosomes;
    }

    private boolean nearGateHeuristic(Flight info) {
        return info.getDepartureTime() - info.getArrivalTime() <= 1.5 * 60 * 60;
    }

    private boolean farGateHeuristic(Flight info) {
        return info.getDepartureTime() - info.getArrivalTime() >= 24 * 60 * 60
                || (info.getDepartureTime() - info.getArrivalTime() >= 3 * 60 * 60
                && isInDayTime(info.getArrivalTime()));
    }

    private boolean isInDayTime(int timeStamp) {
        Date date = new Date(timeStamp);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int hour = calendar.get(Calendar.HOUR_OF_DAY);
        return hour > 6 && hour <= 21;
    }

    double evaluate(int[] chromosome) {
        ScheduleSolution solution = new ScheduleSolution(problem);
        for(int i = 0; i < chromosome.length; i++) {
            Flight flight = problem.getFlightAt(i);
            FlightGate gate = problem.getFlightGateAt(chromosome[i]);
            solution.assign(flight, gate);
        }
        return ScheduleEvaluator.geneticScore(solution, totalFlight, totalPeople);
    }

    public ScheduleSolution getSolution() {
        return solution;
    }

    public void run() {
        int iter = 0;
        while(iter++ < maxIterations) {
            this.genotype.evolve();
            System.out.println("current fittest value = "+this.genotype.getFittestValue()
                    +" second = "+this.genotype.secondValue);
        }
        int[] chromosome = this.genotype.getBestChromosome();
        solution = new ScheduleSolution(problem);
        for(int i = 0; i < chromosome.length; i++) {
            Flight flight = problem.getFlightAt(i);
            FlightGate gate = problem.getFlightGateAt(chromosome[i]);
            solution.assign(flight, gate);
        }
    }

}
